package com.bnq.logc.upload

import cn.ucloud.ufile.api.ApiError
import cn.ucloud.ufile.bean.PutObjectResultBean
import cn.ucloud.ufile.bean.UfileErrorBean
import cn.ucloud.ufile.http.BaseHttpCallback
import com.bnq.logc.LogcManager
import com.bnq.logc.config.LogcConfig
import com.bnq.logc.data.*
import com.bnq.logc.db.LogcDao
import com.bnq.logc.db.tmp.LogcTmpDao
import com.bnq.logc.native.NativeLogc
import com.bnq.logc.utils.FileUtil
import com.tencent.cos.xml.exception.CosXmlClientException
import com.tencent.cos.xml.exception.CosXmlServiceException
import com.tencent.cos.xml.model.CosXmlResult
import okhttp3.Request
import java.io.File

object UploadFileManager {

    private var currentTime1 = 0L
    private const val DEBOUNCE_TIME = 3 * 60 * 1000

    private val cosFileUploadManager by lazy { CosFileUploadManager() }
    private val ucFileUploadManager by lazy { UfileFileUploadManager() }
    private val fileUploadServer by lazy { LogcManager.getConfig().mFileUploadServer }

    @Synchronized
    fun checkAndUploadFileLast1(){
        if(!LogcManager.getConfig().mIsUploadLog) return
        if(currentTime1 + DEBOUNCE_TIME < System.currentTimeMillis()){
            currentTime1 = System.currentTimeMillis()
            checkAndUploadFiles(FileUtil.getLastFiles(LogcManager.getConfig().getParentFileDir(),".log",1))
        }
    }

    @Synchronized
    fun checkAndUploadFileLast7(callback:((CheckUploadResult)->Unit)? = null){
        if(!LogcManager.getConfig().mIsUploadLog){
            callback?.invoke(CheckUploadResult(null,Exception("关闭了上传log日志"),LogcManager.getConfig().mIsUploadLog))
            return
        }
        checkAndUploadFiles(FileUtil.getLastFiles(LogcManager.getConfig().getParentFileDir(),".log",7),callback)
    }

    @Synchronized
    fun checkAndUploadFileLastCount(count:Int,callback:((CheckUploadResult)->Unit)? = null){
        if(!LogcManager.getConfig().mIsUploadLog) {
            callback?.invoke(CheckUploadResult(null,null,false))
            return
        }
        checkAndUploadFiles(FileUtil.getLastFiles(LogcManager.getConfig().getParentFileDir(),".log",count),callback)
    }

    @Synchronized
    fun checkAndUploadFiles(files:List<File>,callback:((CheckUploadResult)->Unit)? = null){
        if(!LogcManager.getConfig().mIsUploadLog) {
            callback?.invoke(CheckUploadResult(null,null,false))
            return
        }
        if(files.isNotEmpty()){
            try{
                val localDBFileInfoCount = getLocalDBFileInfoCount(files.size)
                val changeFile = checkChangeFiles(files,localDBFileInfoCount)
                if(changeFile.isEmpty()){
                    callback?.invoke(CheckUploadResult(null,Exception("无需上传文件"),true))
                    return
                }
                uploadFiles(changeFile){
                    callback?.invoke(CheckUploadResult(it,null,true))
                }
            }catch (e:Exception){
                e.printStackTrace()
                callback?.invoke(CheckUploadResult(null,e,true))
            }
        }else{
            callback?.invoke(CheckUploadResult(null,Exception("无上传文件"),true))
        }
    }

    private fun checkChangeFiles(files:List<File>,recordFileInfo:List<FileInfoBean>):List<File>{
        if(files.isEmpty()){
            return files
        }
        val result = mutableListOf<File>()
        files.forEach {
            if(isChangeFile(it,recordFileInfo)){
                result.add(it)
            }
        }
        return result
    }

    private fun isChangeFile(file:File,recordFileInfo:List<FileInfoBean>):Boolean{
        if(recordFileInfo.isEmpty()){
            return true
        }
        var hasRecord = false
        recordFileInfo.forEach {
            if(it.path == file.absolutePath){
                hasRecord = true
                if(((FileUtil.isTodayFile(file) && it.fileLastUpdateSize < file.length()) || it.fileLastUpdateSize != file.length())){
                    return true
                }
            }
        }

        return !hasRecord
    }

    private fun getLocalDBFileInfo7():List<FileInfoBean>{
        return LogcDao.queryList(7)
    }

    private fun getLocalDBFileInfoCount(count: Int):List<FileInfoBean>{
        return LogcDao.queryList(count)
    }

    private fun updateDBFileInfo(file:File){
        val fileNameKey = FileUtil.getFileKey(file)
        val querySingle =
            LogcDao.querySingleByPath(file.absolutePath)
        if(querySingle == null){
            LogcDao.inset(FileInfoBean(0,fileNameKey,file.absolutePath,file.length(),null,NativeLogc.getFileCreateTimeStamp(file).let { if(it <= 0) System.currentTimeMillis() else it },System.currentTimeMillis()))
        }else{
            LogcDao.update(querySingle.apply { fileLastUpdateSize = file.length();modifyTime = System.currentTimeMillis() })
        }
    }

    private fun uploadFiles(files:List<File>,callback:((List<UploadResult>)->Unit)? = null){
        if(files.isEmpty()){
            callback?.invoke(emptyList())
            return
        }
        var uploadResults = mutableListOf<UploadResult>()
        val lock = Any()
        var size = files.size
        var count = 0
        files.forEach { file ->
            when(fileUploadServer){
                LogcConfig.FileUploadServer.UC -> {
                    var cloudName = ""
                    cloudName = ucFileUploadManager.upload(file,object :FileUploadResultCallback<PutObjectResultBean?,ApiError?,UfileErrorBean?>{
                        override fun onSuccess(result: PutObjectResultBean?) {
                            updateDBFileInfo(file)
                            synchronized(lock) {
                                uploadResults.add(UploadResult(file, cloudName,UploadStatus.Success))
                                count ++
                                if(count == size){
                                    callback?.invoke(uploadResults)
                                }
                            }
                        }

                        override fun onFail(e1: ApiError?, e2: UfileErrorBean?) {
                            synchronized(lock) {
                                uploadResults.add(UploadResult(file,cloudName,UploadStatus.Fail, UfileUploadError(null,e1,e2)))
                                count ++
                                if(count == size){
                                    callback?.invoke(uploadResults)
                                }
                            }
                        }
                    })
                }
                LogcConfig.FileUploadServer.COS -> {
                    var cloudName = ""
                    cloudName = cosFileUploadManager.upload(file,object :FileUploadResultCallback<CosXmlResult?, CosXmlClientException?, CosXmlServiceException?>{
                        override fun onSuccess(result: CosXmlResult?) {
                            updateDBFileInfo(file)
                            synchronized(lock) {
                                uploadResults.add(UploadResult(file,cloudName,UploadStatus.Success))
                                count ++
                                if(count == size){
                                    callback?.invoke(uploadResults)
                                }
                            }
                        }

                        override fun onFail(e1: CosXmlClientException?, e2: CosXmlServiceException?) {
                            synchronized(lock) {
                                uploadResults.add(UploadResult(file,cloudName,UploadStatus.Fail, CosFileUploadError(e1,e2)))
                                count ++
                                if(count == size){
                                    callback?.invoke(uploadResults)
                                }
                            }
                        }

                    })
                }
            }
        }
    }


    //*************************************************************

    fun checkAndUploadTmpFile(files:List<File>,cloudPathNames:List<String>,callback:((CheckUploadResult)->Unit)? = null){
        if(files.isNotEmpty()){
            try{
                uploadTmpFiles(files, cloudPathNames){
                    callback?.invoke(CheckUploadResult(it,null,true))
                }
            }catch (e:Exception){
                e.printStackTrace()
                callback?.invoke(CheckUploadResult(null,e,true))
            }
        }else{
            callback?.invoke(CheckUploadResult(null,Exception("无上传文件"),true))
        }
    }

    private fun isTmpChangeFile(file:File,recordFileInfo:List<FileInfoBean?>):Boolean{
        if(recordFileInfo.isEmpty()){
            return true
        }
        val find = recordFileInfo.find { it != null && it.path == file.absolutePath }
        if(find == null){
            return true
        }else{
            if(find.fileLastUpdateSize < file.length() || find.fileLastUpdateSize != file.length()){
                return true
            }
        }
        return false
    }

    fun queryTmpAllFileInfo():List<FileInfoBean>{
        return LogcTmpDao.queryAll()
    }

    fun queryTmpFileFilesInfo(paths:List<File>):List<FileInfoBean?>{
        val p = paths.map { it.absolutePath }
        return queryTmpStringFilesInfo(p)
    }

    fun queryTmpStringFilesInfo(paths:List<String>):List<FileInfoBean?>{
        val result = mutableListOf<FileInfoBean?>()
        paths.forEach { path->
            val info = queryTmpSingleFileInfo(path)
            result.add(info)
        }
        return result
    }

    fun queryTmpSingleFileInfo(path:String):FileInfoBean?{
        return LogcTmpDao.querySingleByPath(path)
    }

    private fun updateTmpDBFileInfo(file:File){
        val fileNameKey = FileUtil.getFileKey(file)
        val querySingle =
            LogcTmpDao.querySingleByPath(file.absolutePath)
        if(querySingle == null){
            LogcTmpDao.inset(FileInfoBean(0,fileNameKey,file.absolutePath,file.length(),null,NativeLogc.getFileCreateTimeStamp(file).let { if(it <= 0) System.currentTimeMillis() else it },System.currentTimeMillis()))
        }else{
            LogcTmpDao.update(querySingle.apply { fileLastUpdateSize = file.length();modifyTime = System.currentTimeMillis() })
        }
    }

    fun uploadTmpFiles(files:List<File>,cloudPathNames:List<String>,callback:((List<UploadResult>)->Unit)? = null){
        if(files.isEmpty()){
            callback?.invoke(emptyList())
            return
        }
        var uploadResults = mutableListOf<UploadResult>()
        val lock = Any()
        var size = files.size
        var count = 0
        val getCloudPathName:(Int,File)->String = {index,file->
            if(index >= cloudPathNames.size){
                "logc/tmp/${file.name}"
            }else{
                val pathName = cloudPathNames[index]
                pathName.ifBlank {
                    "logc/tmp/${file.name}"
                }
            }
        }
        val localDBFileInfo = queryTmpFileFilesInfo(files)
        val commonErrorFun = {file:File,cloudName:String,errorMsg:String->
            synchronized(lock) {
                uploadResults.add(UploadResult(file,cloudName,UploadStatus.Fail,UfileUploadError(Exception(errorMsg),null,null)))
                count ++
                if(count == size){
                    callback?.invoke(uploadResults)
                }
            }
        }
        files.forEachIndexed {index, file ->
            val cloudPathName = getCloudPathName(index,file)
            if(!file.exists()){
                commonErrorFun(file,cloudPathName,"文件不存在")
            }else if(!file.isFile){
                commonErrorFun(file,cloudPathName,"上传的必须是文件")
            }else if(!isTmpChangeFile(file,localDBFileInfo)){
                commonErrorFun(file,cloudPathName,"远程文件已是最新文件，无需上传")
            }else{
                when(fileUploadServer){
                    LogcConfig.FileUploadServer.UC -> {
                        var _cloudName = ""
                        _cloudName = ucFileUploadManager.uploadCommon(file,cloudPathName,object :FileUploadResultCallback<PutObjectResultBean?,ApiError?,UfileErrorBean?>{
                            override fun onSuccess(result: PutObjectResultBean?) {
                                updateTmpDBFileInfo(file)
                                synchronized(lock) {
                                    uploadResults.add(UploadResult(file,_cloudName,UploadStatus.Success))
                                    count ++
                                    if(count == size){
                                        callback?.invoke(uploadResults)
                                    }
                                }
                            }

                            override fun onFail(e1: ApiError?, e2: UfileErrorBean?) {
                                synchronized(lock) {
                                    uploadResults.add(UploadResult(file,_cloudName,UploadStatus.Fail,UfileUploadError(null,e1,e2)))
                                    count ++
                                    if(count == size){
                                        callback?.invoke(uploadResults)
                                    }
                                }
                            }
                        })
                    }
                    LogcConfig.FileUploadServer.COS -> {
                        var _cloudName = ""
                        _cloudName = cosFileUploadManager.uploadCommon(file,cloudPathName,object :FileUploadResultCallback<CosXmlResult?, CosXmlClientException?, CosXmlServiceException?>{
                            override fun onSuccess(result: CosXmlResult?) {
                                updateTmpDBFileInfo(file)
                                synchronized(lock) {
                                    uploadResults.add(UploadResult(file,_cloudName,UploadStatus.Success))
                                    count ++
                                    if(count == size){
                                        callback?.invoke(uploadResults)
                                    }
                                }
                            }

                            override fun onFail(e1: CosXmlClientException?, e2: CosXmlServiceException?) {
                                synchronized(lock) {
                                    uploadResults.add(UploadResult(file,_cloudName,UploadStatus.Fail,CosFileUploadError(e1,e2)))
                                    count ++
                                    if(count == size){
                                        callback?.invoke(uploadResults)
                                    }
                                }
                            }

                        })
                    }
                }
            }
        }
    }

    fun filterNotRecordFileInTmp(files:List<File>):List<File>{
        val result = mutableListOf<File>()
        files.forEach { file->
            val querySingle =
                LogcTmpDao.querySingleByPath(file.absolutePath)
            if(querySingle == null){
                result.add(file)
            }
        }
        return result
    }

}